#include "nosal/core/error_code.h"
#include "test_domain.h"
#include "gtest/gtest.h"

#include <type_traits>

using namespace netaos;

TEST(ErrorCodeTest, StaticTypeProperties)
{
    EXPECT_TRUE(std::is_trivially_copy_constructible<nosal::core::ErrorCode>::value);
    EXPECT_TRUE(std::is_trivially_move_constructible<nosal::core::ErrorCode>::value);
    EXPECT_TRUE(std::is_trivially_copy_assignable<nosal::core::ErrorCode>::value);
    EXPECT_TRUE(std::is_trivially_move_assignable<nosal::core::ErrorCode>::value);
    EXPECT_TRUE(std::is_trivially_destructible<nosal::core::ErrorCode>::value);
    EXPECT_TRUE(std::is_nothrow_destructible<nosal::core::ErrorCode>::value);

    EXPECT_TRUE(std::is_trivially_copyable<nosal::core::ErrorCode>::value);
    EXPECT_TRUE(std::is_standard_layout<nosal::core::ErrorCode>::value);
}

TEST(ErrorCodeTest, Constructor1_WorksConstexpr)
{
    using namespace nosal::core;

    constexpr ErrorDomain const& domain = GetTestErrorDomain();

    // 2-arguments ctor
    constexpr ErrorCode const error1b(TestErrc::some_error, 11);
    constexpr ErrorDomain const& errorDomain1b = error1b.Domain();
    constexpr ErrorDomain::CodeType const code1b = error1b.Value();
    constexpr ErrorDomain::SupportDataType const errorExtra1b = error1b.SupportData();
    EXPECT_EQ(errorDomain1b, domain);
    EXPECT_EQ(code1b, static_cast<ErrorDomain::CodeType>(TestErrc::some_error));
    EXPECT_EQ(errorExtra1b, 11);

    // 1-argument ctor
    constexpr ErrorCode const error1c(TestErrc::some_error);
    constexpr ErrorDomain const& errorDomain1c = error1c.Domain();
    constexpr ErrorDomain::CodeType const code1c = error1c.Value();
    constexpr ErrorDomain::SupportDataType const errorExtra1c = error1c.SupportData();
    EXPECT_EQ(errorDomain1c, domain);
    EXPECT_EQ(code1c, static_cast<ErrorDomain::CodeType>(TestErrc::some_error));
    EXPECT_EQ(errorExtra1c, 0);
}

TEST(ErrorCodeTest, Constructor2_WorksConstexpr)
{
    using namespace nosal::core;

    constexpr ErrorDomain const& domain = GetTestErrorDomain();
    constexpr ErrorDomain::CodeType const code = static_cast<ErrorDomain::CodeType>(TestErrc::some_error);

    // 3-arguments ctor
    constexpr ErrorCode const error3b(code, GetTestErrorDomain(), 14);
    constexpr ErrorDomain const& errorDomain3b = error3b.Domain();
    constexpr ErrorDomain::CodeType const code3b = error3b.Value();
    constexpr ErrorDomain::SupportDataType const errorExtra3b = error3b.SupportData();
    EXPECT_EQ(errorDomain3b, domain);
    EXPECT_EQ(code3b, static_cast<ErrorDomain::CodeType>(TestErrc::some_error));
    EXPECT_EQ(errorExtra3b, 14);

    // 2-arguments ctor
    constexpr ErrorCode const error3c(code, GetTestErrorDomain());
    constexpr ErrorDomain const& errorDomain3c = error3c.Domain();
    constexpr ErrorDomain::CodeType const code3c = error3c.Value();
    constexpr ErrorDomain::SupportDataType const errorExtra3c = error3c.SupportData();
    EXPECT_EQ(errorDomain3c, domain);
    EXPECT_EQ(code3c, static_cast<ErrorDomain::CodeType>(TestErrc::some_error));
    EXPECT_EQ(errorExtra3c, 0);
}

TEST(ErrorCodeTest, Constructor_Implicit_WorksConstexpr)
{
    using namespace nosal::core;

    constexpr ErrorCode const error = TestErrc::some_error;

    constexpr ErrorDomain::CodeType const errorValue = error.Value();
    constexpr ErrorDomain::SupportDataType const errorExtra = error.SupportData();
    constexpr ErrorDomain const& errorDomain = error.Domain();
    EXPECT_EQ(errorValue, static_cast<ErrorDomain::CodeType>(TestErrc::some_error));
    EXPECT_EQ(errorExtra, 0);

    constexpr ErrorDomain const& domain = GetTestErrorDomain();
    EXPECT_EQ(errorDomain, domain);
}

TEST(ErrorCodeTest, Comparison_IsConstexpr)
{
    constexpr nosal::core::ErrorCode const error1 = TestErrc::some_error;
    constexpr nosal::core::ErrorCode const error2 = TestErrc::some_error;

    constexpr bool const errorsAreSame = (error1 == error2);
    constexpr bool const errorsAreNotSame = (error1 != error2);
    EXPECT_TRUE(errorsAreSame);
    EXPECT_FALSE(errorsAreNotSame);
}

TEST(ErrorCodeTest, Equals_ConsidersDomain)
{
    nosal::core::ErrorDomain::CodeType const code = 42;
    nosal::core::ErrorCode const error1(code, GetTestErrorDomain());
    //nosal::core::ErrorCode const error2(code, nosal::core::GetFutureErrorDomain());
    //EXPECT_NE(error1, error2);

    nosal::core::ErrorCode const error3{static_cast<TestErrc>(code)};
    EXPECT_EQ(error3, error1);
}

TEST(ErrorCodeTest, Equals_ConsidersValue)
{
    nosal::core::ErrorDomain::CodeType const code1 = 42;
    nosal::core::ErrorDomain::CodeType const code2 = 43;

    nosal::core::ErrorCode const error1(code1, GetTestErrorDomain());
    nosal::core::ErrorCode const error2(code2, GetTestErrorDomain());
    EXPECT_NE(error1, error2);

    nosal::core::ErrorCode const error3(code1, GetTestErrorDomain());
    EXPECT_EQ(error3, error1);
}

TEST(ErrorCodeTest, Equals_IgnoresSupportData)
{
    nosal::core::ErrorDomain::CodeType const code = 42;
    nosal::core::ErrorCode const error1(code, GetTestErrorDomain());
    nosal::core::ErrorCode const error2(code, GetTestErrorDomain(), 1234);
    nosal::core::ErrorCode const error3(code, GetTestErrorDomain(), 56789);
    EXPECT_EQ(error1, error2);
    EXPECT_EQ(error1, error3);
    EXPECT_EQ(error2, error3);
}

TEST(ErrorCodeTest, CopyCtor_Works)
{
    using namespace nosal::core;

    ErrorCode const error1 = TestErrc::some_error;
    ErrorCode const error2(error1);

    EXPECT_EQ(error2.Value(), static_cast<ErrorDomain::CodeType>(TestErrc::some_error));
}

#if 0
static void Sink(nosal::core::ErrorCode const&)
{
    // Nothing to be done
}
TEST(ErrorCodeTest, CopyAssignment_Works)
{
    using namespace nosal::core;

    ErrorCode error1 = TestErrc::some_error;
    Sink(error1);  // suppress a cppcheck redundantAssignment warning
    ErrorCode const error2 = future_errc::broken_promise;

    error1 = error2;
    //EXPECT_THAT(error1.Value(), Eq(static_cast<ErrorDomain::CodeType>(future_errc::broken_promise)));
    EXPECT_EQ(error1.Value(), static_cast<ErrorDomain::CodeType>(future_errc::broken_promise));
}
#endif
TEST(ErrorCodeTest, MoveCtor_Works)
{
    using namespace nosal::core;

    ErrorCode const error(ErrorCode{TestErrc::some_error});
    EXPECT_EQ(error.Value(), static_cast<ErrorDomain::CodeType>(TestErrc::some_error));
}
#if 0
TEST(ErrorCodeTest, MoveAssignment_Works)
{
    using namespace nosal::core;

    ErrorCode error1 = TestErrc::some_error;
    Sink(error1);  // suppress a cppcheck redundantAssignment warning
    ErrorCode error2 = future_errc::broken_promise;

    error1 = std::move(error2);
    //EXPECT_THAT(error1.Value(), Eq(static_cast<ErrorDomain::CodeType>(future_errc::broken_promise)));
    EXPECT_EQ(error1.Value(), static_cast<ErrorDomain::CodeType>(future_errc::broken_promise));
}
#endif

TEST(ErrorCodeTest, ThrowAsException_Works)
{
    nosal::core::ErrorCode const error = TestErrc::some_error;

    bool wasThrown = false;
    try {
        error.ThrowAsException();
    } catch (TestException const& ex) {
        wasThrown = true;
        EXPECT_EQ(ex.Error(), error);
    }
    EXPECT_TRUE(wasThrown);
}
